#include "Supporting/Enums/EnumLevelType.h"
#include "Character.h"
#include "CharacterAI.h"
#include "Map/GameMap.h"
#include "Supporting/SoundLoader.h"
#include "Supporting/Enums/EnumSoundType.h"

//#include "SimpleAudioEngine.h"

using namespace cocos2d;
//using namespace CocosDenshion;

//========================== C O N S T R U C T O R ==============================
Character::Character()
{

}

void Character::init(char* filename, GameMap* pMap)
{

	// SYNTHESIZE: _nextPosition, _vector, _touch, _speedX, _speedY, _obstacleHitChance, _charState, _speedMultiplier
	//CHARACTER AI
	//setupAI();
	CCLog("before ai");
	_pCharBaseAI = new CharacterAI(pMap);// AI class to decide on actions
	CCLog("after ai");
	//CHARACTER STATE
	_stopTimer = -1;
	_obstacleHitChance = BASE_OBSTACLE_HIT_CHANCE;
	_charState = RUN; //start off running

	//MOVEMENT
	CCLog("before stats");
	_maxMoveDistance = pMap->getPixelsPerGrid();
	_amountMoved = 0;
	_speedX = BASE_SPEED_X * pMap->getScaleFactor();
	_speedY = BASE_SPEED_Y * pMap->getScaleFactor();
	_speedMultiplier = BASE_SPEED_MULTIPLIER;
	_directorSpeedMultiplier = BASE_SPEED_MULTIPLIER;

	CCLog("after stats");
	//HEALTH
	_health = BASE_HEALTH;

	//IMMUNITY
	_immunityTimer = 0;
	_isImmune = false;

	// Get initial position from GameMap
	CCLog("Getting start point ");
	this->setPosition(pMap->getCharStartPoint());
	CCLog("Got start point");
	//load all character sprites
	CCLog("initializing character sprites");
	initCharacterSprites(filename, pMap);
	CCLog("initialized sprites");
}

//========================== D E S T R U C T O R ==============================
Character::~Character(void)
{
	delete _pCharBaseAI;
	_pCharBaseAI = NULL;
}

//========================== S T A T I C   F U N C T I O N S ==============================
/*!
    @function createSpriteFrame
    @result		creates a CCSpriteFrame based on this character
 */
CCSpriteFrame* Character::createSpriteFrame(char* filename)
{
	return CCSpriteFrame::create(filename, CCRect(0, 100,100,100) );
}

//========================== P R I V A T E   F U N C T I O N S ==============================
/*!
    @function loadSprite
    @result		loads the animation for the character and start playing it
 */
void Character::loadSprite(char* filename)
{
	_filename = filename;
	playRunningAnimation();

}

void Character::playRunningAnimation()
{
	CCAnimation *runningAnimation = CCAnimation::create();

	//load the frames of animation
	for (int i = 0; i < 4; i++)
	{
		CCSpriteFrame *frame = CCSpriteFrame::create(_filename, CCRect(i*100, 0, 100, 100));
		runningAnimation->addSpriteFrame(frame);
	}
	//setup animation properties
	runningAnimation->setDelayPerUnit(0.15); // delay between each frame in seconds
	runningAnimation->setLoops(10000);
	runningAnimation->setRestoreOriginalFrame(true); //loop

	_pRunningAnimate = CCAnimate::create(runningAnimation);

	//start animating the running
	this->stopAllActions();
	this->runAction(_pRunningAnimate);
}

void Character::playInjuredAnimation()
{
	//load injured animation

	CCAnimation *injuredAnimation = CCAnimation::create();

	//load the frames of animation
	for (int i = 0; i < 2; i++)
	{
		CCSpriteFrame *frame = CCSpriteFrame::create(_filename, CCRect(i*100, 100, 100, 100));
		injuredAnimation->addSpriteFrame(frame);
	}
	//setup animation properties
	injuredAnimation->setDelayPerUnit(0.1); // delay between each frame in seconds
	injuredAnimation->setLoops(10000);
	injuredAnimation->setRestoreOriginalFrame(true); //loop

	_pInjuredAnimate = CCAnimate::create(injuredAnimation);


	//start animating the running
	this->stopAllActions();
	this->runAction(_pInjuredAnimate);
}

/*!
    @function initCharacterSprites
    @param		filename: char*
    			filename of the sprite sheet of the character
    @param		pMap:  GameMap*
    			a reference to the game map this character is running on
    @result		returns a character with loaded sprites
 */
void Character::initCharacterSprites(char* filename, GameMap* pMap)
{
	//setup sprite
	this->initWithSpriteFrame(createSpriteFrame(filename));
	this->loadSprite(filename);
	this->setScale(pMap->getScaleFactor());
	this->autorelease();
}

/*!
    @function runStraight
    @param		timeDiff:float
				time difference since the last call
    @result		updates the amount moved, but do not move the character. map is scrolling instead
 */
void Character::runStraight(float timeDiff)
{
	//move horizontally
	float moveAmount = getXSpeed(timeDiff);
	destinationCheck(moveAmount);
}

/*!
    @function runUp
    @param		timeDiff:float
				time difference since the last call
    @result		moves the character UP based on its current speed and multiplier.
    			cat moves straight again after moving 1 grid
 */
void Character::runUp(float timeDiff)
{
	float moveAmount = getYSpeed(timeDiff);

	//update position to move towards top of screen based on speed (UP, PLUS)
	this->setPositionY(this->getPositionY() + destinationCheck(moveAmount));
}

/*!
    @function runDown
    @param		timeDiff:float
				time difference since the last call
    @result		moves the character DOWN based on its current speed and multiplier.
    			cat moves straight again after moving 1 grid
 */
void Character::runDown(float timeDiff)
{
	float moveAmount = getYSpeed(timeDiff);

	//update position to move towards bottom of screen based on speed (DOWN, MINUS)
	this->setPositionY(this->getPositionY() - destinationCheck(moveAmount));
}

/*!
    @function hitObstacle
    @param		timeDiff:float
				time difference since the last call
    @result		do nothing
 */
void Character::hitObstacle(float timeDiff)
{
	//do nothing
}

/*!
    @function stop
    @param		timeDiff:float
				time difference since the last call
    @result		slows the speed of the character by a factor of the STOP_SPEED_MULTIPLIER
    			for the duration of STOP_DURATION
    			restores speed back to normal after that.
    			changes state to run after the duration
 */
void Character::stop(float timeDiff)
{
	//if the timer has started
	if (_stopTimer >= 0)
	{
		//countdown the timer
		_stopTimer -= timeDiff;

		//reset to normal speed when timer ends
		if (_stopTimer <= 0)
		{
			_stopTimer = -1;
			//reset the speed multiplier
			_speedMultiplier *= STOP_SPEED_MULTIPLIER;
			_charState = RUN;
		}
	}
	else
	{
		//start the timer and reduce the multiplier
		_stopTimer = STOP_DURATION;
		_speedMultiplier /= STOP_SPEED_MULTIPLIER;
	}
}

void Character::dead(float timeDiff)
{
	//TODO: dead animation
}

/*!
    @function destinationCheck
    @param		moveAmount: float
				the amount to move on each update call
    @result		check the remaining distance to destination and changes
    			the amount to move if overshot the destination.
    			changes state to RUN if reached destination
 */
float Character::destinationCheck(float moveAmount)
{
	_amountMoved += moveAmount;

	if( _amountMoved >= _maxMoveDistance)
	{
		//remove the excess move amount
		moveAmount -= _amountMoved - _maxMoveDistance;
		_amountMoved = 0;
		setCharState(RUN);
	}
	return moveAmount;
}

/*!
    @function updateImmunity
    @param		timeDiff:float
				time difference since the last call
    @result		reduces the immunity timer if it has started.
    			sets immunity to false if the timer expired
 */
void Character::updateImmunity(float timeDiff)
{
	if (_isImmune)
	{
		//TODO: immunity BLINKING animation here

		//reduce timer
		_immunityTimer -= timeDiff;
		//timer has finished
		if (_immunityTimer <= 0)
		{
			_isImmune = false;
			playRunningAnimation();
		}
	}
}


//========================== P U B L I C   F U N C T I O N S ==============================
/*!
    @function update
    @param		timeDiff:float
				time difference between the last update call
    @result		base on the state the character is in, call corresponding functions
 */
void Character::update(float timeDiff)
{
	//UPDATE TIMERS
	updateImmunity(timeDiff); //immunity timer

	// Update AI's state if required
	_charState = _pCharBaseAI->getState(timeDiff, this);


	//act according to state
	switch(_charState)
	{
	//run straight 1 grid
	case RUN_STRAIGHT:
		runStraight(timeDiff);
		break;

		// Run up 1 grid
	case RUN_UP:
		runUp(timeDiff);
		break;

		// Run down 1 grid
	case RUN_DOWN:
		runDown(timeDiff);
		break;

		//trying to hit obstacle
	case HIT_OBSTACLE:
		hitObstacle(timeDiff);
		break;

		// Stop
	case STOP:
		stop(timeDiff);
		break;

		// Character is dead
	case DEAD:
		dead(timeDiff);
		break;
	}
}


/*!
    @function reduceHealth
    @param		healthReduction:int
				the amount of health to reduce from the character
    @result		reduces the specified amount of health from the character if it is not immune.

 */
void Character::reduceHealth(int health)
{
	//if not currently immune
	if(!_isImmune)
	{
		_health -= health;
		//play injured animation
		playInjuredAnimation();

		//check if character is dead
		if(_health <= 0)
		{
			SoundLoader::playSound(CHAR_DEATH_SOUND);
			//_charState = DEAD;
			return;
		}

		SoundLoader::playSound(CHAR_INJURED_SOUND);

		//Blink the character to let player know he is injured
		//CCActionInterval* blink = CCBlink::create(IMMUNITY_DURATION, 4);
		//this->runAction(blink);

		//restart immunity duration
		_immunityTimer = IMMUNITY_DURATION;
		_isImmune = true;
	}
}

/*!
    @function getRect
    @result		returns a CCRect based on this sprite with a reduced size according to the RECT_BUFFER_RADIUS.
    			mainly used for a more accurate collision detection
 */
CCRect Character::getRect()
{
	return CCRectMake(this->getPosition().x - (this->getContentSize().width/2) + RECT_BUFFER_RADIUS,
			this->getPosition().y - (this->getContentSize().height/2) + RECT_BUFFER_RADIUS,
			this->getContentSize().width - RECT_BUFFER_RADIUS *2,
			this->getContentSize().height - RECT_BUFFER_RADIUS *2);
}

/*!
    @function getXSpeed
    @param		timeDiff:float
				time difference between the last update call
    @result		gets the X speed of the character (after multiplying the time diff and multiplier)
 */
float Character::getXSpeed(float timeDiff)
{
	return (_speedX * timeDiff) * _speedMultiplier * _directorSpeedMultiplier;
}

/*!
    @function getYSpeed
    @param		timeDiff:float
				time difference between the last update call
    @result		gets the Y speed of the character (after multiplying the time diff and multiplier)
 */
float Character::getYSpeed(float timeDiff)
{
	return (_speedY * timeDiff) * _speedMultiplier * _directorSpeedMultiplier;
}

void Character::updateDirectorObstacle(float obstacleHitChance)
{
	//TODO
	if( obstacleHitChance > BASE_OBSTACLE_HIT_CHANCE && obstacleHitChance <= 100)
	{
		this->setObstacleHitChance(obstacleHitChance);
	}
}

void Character::updateDirectorSpeed(float speedMultiplier)
{
	//TODO
	if( speedMultiplier > 0)
	{
		this->setDirectorSpeedMultiplier(speedMultiplier);
	}
}
